/* 材质加载器：把 materialId 转换为 THREE.MeshStandardMaterial，附带缓存 */
// 第三方库
import * as THREE from "three";
// 项目公用
import {
  type CachedTextureLoader,
  cachedTextureLoader,
} from "./CachedTextureLoader";
import { color, tree } from "./utils/index";
// 当前项目
import type { ModelConfig, SceneJson, MaterialConfig } from "./types";
// 资产文件
import urlTextureDiffuse from "./assets/default-ground-material/stone_tiles_diff_2k.jpg";
import urlTextureNormal from "./assets/default-ground-material/stone_tiles_nor_gl_2k.jpg";
import urlTextureArm from "./assets/default-ground-material/stone_tiles_arm_2k.jpg";
import urlTexturePreview from "./assets/default-ground-material/stone_tiles_preview.jpg";

// 丢失的材质
const MISSING_COLOR = 0xff00ff; // 丢失的材质颜色（品红）

// 默认材质设置
export const defaultGroundMaterialConfig: MaterialConfig = {
  id: "DEFAULT_GROUND",
  name: "默认地面材质",
  previewImage: urlTexturePreview,
  // 材质分类
  substanceType: "其他",
  positionType: ["其他"],
  sourceType: "人造",
  statusType: "干净",
  // 材质设置
  tintColor: [1.0, 1.0, 1.0],
  isTransparent: false,
  isDoubleSide: false,
  // 贴图
  diffuseMapUrl: urlTextureDiffuse,
  normalMapUrl: urlTextureNormal,
  emissiveMapUrl: "",
  aoMapUrl: urlTextureArm,
  roughnessMapUrl: urlTextureArm,
  metalnessMapUrl: urlTextureArm,
  // 强度值
  emissiveIntensity: 1, // 自发光强度，默认为 1，范围 0-2
  aoMapIntensity: 1, // AO贴图强度，默认为 1，范围 0-1
  roughnessValue: 1, // 粗糙度，默认为 1，范围 0-1
  metalnessValue: 0, // 金属度，默认为 0，范围 0-1
};

export class MaterialLoader {
  cachedTextureLoader: CachedTextureLoader;
  constructor(userCachedTextureLoader?: CachedTextureLoader) {
    if (userCachedTextureLoader)
      this.cachedTextureLoader = userCachedTextureLoader;
    else this.cachedTextureLoader = cachedTextureLoader;
  }

  /**
   * 材质缓存
   */
  cache: { [materialId: string]: THREE.MeshStandardMaterial } = {};

  /**
   * 加载一个材质并缓存
   * @param materialId 要加载的材质ID
   * @returns 加载后的 Three.js 标准材质
   */
  load(materialId: string, onLoad?: () => void): THREE.MeshStandardMaterial {
    if (this.cache[materialId]) {
      if (onLoad) setTimeout(onLoad);
      return this.cache[materialId];
    } else {
      const material = new THREE.MeshStandardMaterial();
      this.#loadAndSetMaterial(materialId, material).then(onLoad);
      this.cache[materialId] = material;
      return material;
    }
  }

  /**
   * 加载材质库中的材质
   * @param materialIds 要加载的材质的ID列表
   */
  loadMaterialConfigs?: (materialIds: string[]) => Promise<MaterialConfig[]>;

  /**
   * 延迟加载、设置材质
   * @param materialId 要加载的材质的ID
   * @param material 要设置到那个材质上
   */
  async #loadAndSetMaterial(
    materialId: string,
    material: THREE.MeshStandardMaterial
  ) {
    // 默认材质
    if (materialId == "DEFAULT_GROUND") {
      await this.#applyMaterialConfig(material, defaultGroundMaterialConfig);
    } else if (this.loadMaterialConfigs == null) {
      // 检测是否设置了加载材质设置钩子函数
      console.error(
        "[MaterialLoader]: 使用 load() 需要先设置 loadMaterialConfigs() 方法"
      );
    } else {
      // 加载材质设置
      const materialConfigs = await this.loadMaterialConfigs([materialId]);
      // 应用材质设置
      await this.#applyMaterialConfig(material, materialConfigs[0]);
    }
  }

  /**
   * 应用材质设置到一个材质上
   * @param material 要应用材质设置的材质
   * @param materialConfig 材质设置
   */
  async #applyMaterialConfig(
    material: THREE.MeshStandardMaterial,
    materialConfig: MaterialConfig | undefined
  ) {
    if (materialConfig == null) {
      material.color.set(MISSING_COLOR);
      return;
    }
    // console.log('加载到的材质设置', materialConfig)
    // 设置：半透明、双面
    material.transparent = materialConfig.isTransparent;
    material.side = materialConfig.isDoubleSide
      ? THREE.DoubleSide
      : THREE.FrontSide;
    // 贴图：6种
    if (materialConfig.diffuseMapUrl) {
      material.map = (await this.cachedTextureLoader.loadSrgbAsync(
        materialConfig.diffuseMapUrl
      ))!;
    } else {
      material.map = null;
    }
    if (materialConfig.roughnessMapUrl) {
      material.roughnessMap = (await this.cachedTextureLoader.loadLinearAsync(
        materialConfig.roughnessMapUrl
      ))!;
    } else {
      material.roughnessMap = null;
    }
    if (materialConfig.metalnessMapUrl) {
      material.metalnessMap = (await this.cachedTextureLoader.loadLinearAsync(
        materialConfig.metalnessMapUrl
      ))!;
    } else {
      material.metalnessMap = null;
    }
    if (materialConfig.normalMapUrl) {
      material.normalMap = (await this.cachedTextureLoader.loadLinearAsync(
        materialConfig.normalMapUrl
      ))!;
    } else {
      material.normalMap = null;
    }
    if (materialConfig.aoMapUrl) {
      material.aoMap = (await this.cachedTextureLoader.loadLinearAsync(
        materialConfig.aoMapUrl
      ))!;
    } else {
      material.aoMap = null;
    }
    if (materialConfig.emissiveMapUrl) {
      material.emissiveMap = (await this.cachedTextureLoader.loadSrgbAsync(
        materialConfig.emissiveMapUrl
      ))!;
    } else {
      material.emissiveMap = null;
    }
    // 设置：固定
    material.normalScale.set(1, -1); // UV上下反了，导致法线贴图上下反了，在此修正
    // 设置：5个
    material.color.set(color.normRgbToHexNum(materialConfig.tintColor));
    material.roughness = materialConfig.roughnessValue;
    material.metalness = materialConfig.metalnessValue;
    material.aoMapIntensity = materialConfig.aoMapIntensity;
    material.emissiveIntensity = materialConfig.emissiveIntensity;
    // 标记为需要更新
    material.needsUpdate = true;
  }

  /**
   * 预加载一批材质，用于加载场景的情况
   * @param materialIds 要预加载的所有材质ID
   */
  async preload(materialIds: string[]) {
    if (this.loadMaterialConfigs == null) {
      console.error(
        "[MaterialLoader]: 使用 preload() 需要先设置 loadMaterialConfigs() 方法"
      );
    } else {
      const uncachedMaterialIds = materialIds.filter((materialId) => {
        return this.cache[materialId] == null;
      });
      if (uncachedMaterialIds.length > 0) {
        const uncachedMaterialConfigs = await this.loadMaterialConfigs(
          materialIds
        );
        uncachedMaterialConfigs.forEach((materialConfig) => {
          const material = new THREE.MeshStandardMaterial();
          this.#applyMaterialConfig(material, materialConfig);
          this.cache[materialConfig.id!] = material;
        });
      }
    }
  }

  /**
   * 从场景配置中提取所用到的材质ID列表
   * @param sceneConfig 场景配置
   */
  static extractMaterialIdsFromSceneConfig(sceneConfig: SceneJson): string[] {
    const materialIdMap: { [materialId: string]: true } = {};
    tree.traverse(sceneConfig.models, (item) => {
      const modelConfig: ModelConfig = item.node;
      if (modelConfig?.materialReplacements != null) {
        modelConfig.materialReplacements.forEach((materialReplacement) => {
          materialIdMap[materialReplacement.materialId] = true;
        });
      }
    });
    const materialIds = Object.keys(materialIdMap);
    // console.log('extractMaterialIdsFromSceneConfig: ', materialIds)
    return materialIds;
  }
}

/**
 * 材质加载器的默认实例
 */
export const materialLoader = new MaterialLoader(cachedTextureLoader);
